Fallstudie: Quantitative Methoden in der Saftproduktion¶
WING - HS25 - Prof. Adrian Stämpfli
Kontext¶
Die Apfelsaft GmbH wurde im Jahr 1995 in einem malerischen Dorf in der Schweiz gegründet. Das Unternehmen begann als kleiner Familienbetrieb, der sich auf die Produktion von hochwertigem Apfelsaft aus regionalen Äpfeln spezialisierte. Mit einer Leidenschaft für Qualität und Nachhaltigkeit hat sich die Apfelsaft GmbH schnell einen Namen gemacht und ist heute einer der führenden Hersteller von Apfelsaft in der Region.
Die Apfelsaft GmbH produziert eine breite Palette von Apfelsaftprodukten, darunter naturtrüben Apfelsaft, klaren Apfelsaft und verschiedene Mischsäfte mit anderen Früchten. Das Unternehmen legt grossen Wert auf die Verwendung von lokal angebauten Äpfeln und setzt auf umweltfreundliche Produktionsmethoden. Die Produktionsstätte ist mit modernster Technologie ausgestattet, um höchste Qualitätsstandards zu gewährleisten.
Teilaufgabe: Analyse von Verkaufsdaten¶
Du bist Data Scientist (Wirtschaftsingenieur, Qualitätssicherer, Business Developer, Product Manager) bei der Apfelsaft GmbH und hast die Aufgabe den Verkauf des Obstsaftes besser zu verstehen. Zu diesem Zweck hast du Daten aus dem Verkauf von euren Saftprodukten aus dem Jahr 2023.
Die Daten enthalten Zeilen zu einzelnen Verkaufstransaktionen eines Produkts.
Die Verkaufstransaktion findet an einem Ort (lat, lng) statt, generiert einen Umsatz (umsatz), betrifft ein Produkt (produkt) und findet zu einem bestimmten Zeitpunkt (monat) statt.
Folgenden initialien Fragen kannst Du nachgehen: Wann wird Saft verkauft? Welche Produkte? Wie viel wird verkauft? Wo wird verkauft? Welche Produkte laufen wo besonders gut?
In diesem Jupyter Notebook kannst du mithilfe einer Explorativen Datenanalyse diesen Fragen nachgehen.
Aufbau des Notebooks¶
Im Notebook werden zuerst fiktive Verkaufsdaten generiert. Den Code, welcher die Daten generiert darfst du ignorieren. Deine Aufgabe startet mit dem fertig erstellten Data Frame.
Danach kommt die Explorative Datenanalyse.
Zuerst verschaffst du dir einen Überblick über die Daten. Danach gehst du den einzelnen Fragen nach und vertiefst sie.
Laden der Libraries und Erstellen der fiktiven Verkaufsdaten¶
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.cluster import KMeans
from sklearn.metrics import silhouette_score
# ---------------------------
# 1) Daten generieren
# ---------------------------
np.random.seed(42)
# Parameter
produkte = ["Apfelsaft Klar", "Apfelsaft Trüb", "Apfel-Schorle"]
monate = pd.date_range("2023-01-01", "2023-12-31", freq="MS")
# Cluster-Infos (lat, lng, Varianz, Gewicht, Produkt-Bias)
cluster_info = [
{"center": (50.0, 8.0), "scale": 0.2, "anzahl": 400, "bias": None},
{"center": (52.5, 13.4), "scale": 0.25, "anzahl": 500, "bias": "Apfel-Schorle"},
{"center": (48.1, 11.6), "scale": 0.15, "anzahl": 100, "bias": None}, # Weniger Verkäufe
{"center": (53.6, 9.9), "scale": 0.2, "anzahl": 350, "bias": None}
]
records = []
for cluster in cluster_info:
lat_center, lng_center = cluster["center"]
for i in range(cluster["anzahl"]):
lat = np.random.normal(lat_center, cluster["scale"])
lng = np.random.normal(lng_center, cluster["scale"])
monat = np.random.choice(monate)
if cluster["bias"]:
produkt = np.random.choice(produkte, p=[0.1, 0.1, 0.8]) # Bias für ein Produkt
else:
produkt = np.random.choice(produkte)
umsatz = np.random.randint(100, 1000)
records.append([lat, lng, umsatz, produkt, monat])
df = pd.DataFrame(records, columns=["lat", "lng", "umsatz", "produkt", "monat"])
df.info()
df.describe()
df.head()
<class 'pandas.core.frame.DataFrame'> RangeIndex: 1350 entries, 0 to 1349 Data columns (total 5 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 lat 1350 non-null float64 1 lng 1350 non-null float64 2 umsatz 1350 non-null int64 3 produkt 1350 non-null object 4 monat 1350 non-null datetime64[ns] dtypes: datetime64[ns](1), float64(2), int64(1), object(1) memory usage: 52.9+ KB
| lat | lng | umsatz | produkt | monat | |
|---|---|---|---|---|---|
| 0 | 50.099343 | 7.972347 | 120 | Apfelsaft Klar | 2023-11-01 |
| 1 | 49.953169 | 7.953173 | 187 | Apfel-Schorle | 2023-11-01 |
| 2 | 49.822954 | 7.917562 | 408 | Apfelsaft Trüb | 2023-03-01 |
| 3 | 49.883824 | 7.894966 | 559 | Apfelsaft Klar | 2023-05-01 |
| 4 | 50.004444 | 7.914441 | 289 | Apfel-Schorle | 2023-10-01 |
1.2 Verteilung der Verkäufe nach Produkt¶
sns.countplot(data=df, x='produkt', palette='pastel')
plt.title('Anzahl Verkaufstransaktionen pro Produkt')
plt.xlabel('Produkt')
plt.ylabel('Anzahl Verkäufe')
plt.xticks(rotation=15)
plt.show()
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\547875242.py:1: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.countplot(data=df, x='produkt', palette='pastel')
1.3 Gesamtumsatz pro Produkt¶
sns.barplot(
data=df, x='produkt', y='umsatz',
estimator=sum, errorbar=None,
hue='produkt', palette='Blues_d',
dodge=False, legend=False
)
plt.title('Gesamtumsatz pro Produkt')
plt.xlabel('Produkt')
plt.ylabel('Gesamtumsatz (CHF)')
plt.xticks(rotation=15)
plt.show()
1.4 Durchschnittlicher Umsatz pro Produkt¶
sns.barplot(
data=df, x='produkt', y='umsatz',
estimator=pd.Series.mean, errorbar=None,
hue='produkt', palette='crest',
dodge=False, legend=False
)
plt.title('Durchschnittlicher Umsatz pro Produkt')
plt.xlabel('Produkt')
plt.ylabel('Ø Umsatz (CHF)')
plt.xticks(rotation=15)
plt.show()
1.5 Zeitliche Entwicklung der Umsätze¶
df['monat_num'] = df['monat'].dt.month
sns.lineplot(data=df, x='monat_num', y='umsatz', hue='produkt', marker='o')
plt.title('Zeitliche Entwicklung der Umsätze nach Produkt')
plt.xlabel('Monat')
plt.ylabel('Umsatz (CHF)')
plt.show()
umsatz_monat_prod = df.groupby(['monat_num', 'produkt'], as_index=False)['umsatz'].sum()
sns.lineplot(data=umsatz_monat_prod, x='monat_num', y='umsatz', hue='produkt', marker='o', errorbar=None)
plt.title('Gesamtumsatz pro Monat und Produkt')
plt.xlabel('Monat')
plt.ylabel('Umsatz (CHF)')
plt.show()
sns.scatterplot(
data=df, x='lng', y='lat',
size='umsatz', hue='produkt',
alpha=0.7, palette='Set2'
)
plt.title('Geografische Verteilung der Verkäufe')
plt.xlabel('Längengrad')
plt.ylabel('Breitengrad')
plt.legend(title='Produkt', bbox_to_anchor=(1.05, 1), loc='upper left')
plt.show()
# 1. Cluster-Optimierung: beste Clusterzahl bestimmen
X = df[['lat', 'lng']]
silhouette_scores = []
cluster_range = range(2, 11)
for k in cluster_range:
model = KMeans(n_clusters=k, random_state=42)
labels = model.fit_predict(X)
silhouette_scores.append(silhouette_score(X, labels))
best_k = cluster_range[np.argmax(silhouette_scores)]
print(f"Optimale Clusteranzahl: {best_k}")
# 2. KMeans mit optimaler Clusterzahl
kmeans = KMeans(n_clusters=best_k, random_state=42)
df['cluster'] = kmeans.fit_predict(X)
# 3. Hauptkarte: alle Cluster
plt.figure(figsize=(8, 6))
sns.scatterplot(
data=df, x='lng', y='lat', hue='cluster', size='umsatz',
sizes=(50, 300), palette='viridis', alpha=0.7
)
plt.title('Geografische Cluster der Verkaufsstandorte')
plt.xlabel('Längengrad')
plt.ylabel('Breitengrad')
plt.legend(title='Cluster')
plt.show()
# 4. Cluster-Zentren markieren
centers = kmeans.cluster_centers_
plt.figure(figsize=(8,6))
for i, (lat, lng) in enumerate(centers):
plt.scatter(lng, lat, c='red', s=200, marker='X')
plt.text(lng+0.05, lat+0.05, f'Cluster {i}', color='red', fontsize=10, weight='bold')
plt.title('Cluster-Zentren der Verkaufsstandorte')
plt.xlabel('Längengrad')
plt.ylabel('Breitengrad')
plt.legend(['Cluster-Zentren'])
plt.show()
# 5. Separate Karten je Cluster mit Produkthervorhebung + Barplots
for c in sorted(df['cluster'].unique()):
cluster_data = df[df['cluster'] == c]
# Streudiagramm mit Produkten
plt.figure(figsize=(7, 5))
sns.scatterplot(
data=cluster_data, x='lng', y='lat', hue='produkt',
size='umsatz', sizes=(40, 250), alpha=0.8
)
plt.title(f'Cluster {c}: Produktverteilung')
plt.xlabel('Längengrad')
plt.ylabel('Breitengrad')
plt.legend(title='Produkt', loc='best')
plt.show()
# Barplot: Durchschnittlicher Umsatz pro Produkt in diesem Cluster
plt.figure(figsize=(6,4))
sns.barplot(data=cluster_data, x='produkt', y='umsatz', estimator='sum', palette='crest')
plt.title(f'Cluster {c}: Umsatz pro Produkt')
plt.xlabel('Produkt')
plt.ylabel('Umsatz (CHF)')
plt.show()
Optimale Clusteranzahl: 4
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\3599633746.py:60: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.barplot(data=cluster_data, x='produkt', y='umsatz', estimator='sum', palette='crest')
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\3599633746.py:60: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.barplot(data=cluster_data, x='produkt', y='umsatz', estimator='sum', palette='crest')
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\3599633746.py:60: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.barplot(data=cluster_data, x='produkt', y='umsatz', estimator='sum', palette='crest')
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\3599633746.py:60: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.barplot(data=cluster_data, x='produkt', y='umsatz', estimator='sum', palette='crest')
2.2 Regionale Umsatzschwerpunkte¶
df['region'] = df['lat'].round(0).astype(str) + '/' + df['lng'].round(0).astype(str)
region_umsatz = df.groupby(['region', 'produkt'])['umsatz'].mean().reset_index()
sns.barplot(
data=region_umsatz, x='region', y='umsatz',
hue='produkt', palette='muted',
estimator=pd.Series.mean, errorbar=None
)
plt.title('Durchschnittlicher Umsatz pro Region und Produkt')
plt.xlabel('Region (gerundet)')
plt.ylabel('Ø Umsatz (CHF)')
plt.xticks(rotation=45)
plt.legend(title='Produkt')
plt.show()
2.3 Boxplot – Umsatzverteilung pro Produkt¶
sns.boxplot(
data=df, x='produkt', y='umsatz', palette='cool'
)
plt.title('Umsatzverteilung pro Produkt')
plt.xlabel('Produkt')
plt.ylabel('Umsatz (CHF)')
plt.show()
C:\Users\adrian.staempfli\AppData\Local\Temp\ipykernel_13400\2785082716.py:1: FutureWarning: Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect. sns.boxplot(
2.4 Monatlicher Durchschnittsumsatz (alle Produkte)¶
monthly_avg = df.groupby(df['monat'].dt.month)['umsatz'].mean().reset_index()
sns.lineplot(data=monthly_avg, x='monat', y='umsatz', marker='o', color='darkgreen')
plt.title('Monatlicher Durchschnittsumsatz (alle Produkte)')
plt.xlabel('Monat')
plt.ylabel('Ø Umsatz (CHF)')
plt.show()
2.5 Produktanteile am Gesamtumsatz¶
umsatz_anteile = df.groupby('produkt')['umsatz'].sum().reset_index()
umsatz_anteile['Anteil (%)'] = 100 * umsatz_anteile['umsatz'] / umsatz_anteile['umsatz'].sum()
sns.barplot(
data=umsatz_anteile, x='produkt', y='Anteil (%)',
hue='produkt', palette='viridis',
dodge=False, legend=False,
estimator=pd.Series.mean, errorbar=None
)
plt.title('Anteil am Gesamtumsatz nach Produkt')
plt.xlabel('Produkt')
plt.ylabel('Umsatzanteil (%)')
plt.show()
3. Fazit¶
- Zeitliche Muster
- Alle drei Produkte werden ganzjährig verkauft, mit spürbaren Monat-zu-Monat-Schwankungen vom Umsatz.
- Apfelsaft Klar und Apfel-Schorle zeigen das stabilste Umsatzprofil je Transaktion (geringe Streuung – enge Schattierungsbänder im Lineplot).
- Der gesamte Umsatz pro Produkt und Monat ist gleich volatil bei allen Produkten.
- Räumliche Verteilung / regionale Schwerpunkte
- Die Karte zeigt klare räumliche Cluster: Es existieren mehrere Hotspots, in denen sich Verkaufsereignisse ballen (dichte Punktwolken).
- Überdurchschnittlicher Schorle-Anteil in einer Region, während zwei andere Regionen stärker von „Klar“ dominiert sind.
- Ein Gebiet weist geringere Gesamtdichte/Umsätze auf (kleineres Absatzgebiet bzw. schwächere Präsenz).
- Produktmix & Performance
- Schorle liefert die höchsten Umsätze.
- Apfel-Schorle hat die höchsten Umsätze in der ersten Jahreshälfte. Klar und Trüb steigern die Umsätze in der zweiten Jahreshälfte
- Implikationen.
- Vertrieb & Marketing: In Schorle-Hotspots gezielte Aktionen (Displays, Bundles), in „Klar“-Regionen Sortimentsbreite sichern.